<template>
  <div class="container" :class="{ 'cursor-pointer': isHover }">
    <div ref="scrollBoxRef" class="scroll-box">
      <div
        class="content-box"
        :class="[props.mode === 'horizontal' ? 'flex-row' : 'flex-col']"
        ref="scrollContentRef"
        :style="scrollBoxStyle"
      >
        <slot name="content"></slot>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, onMounted } from "vue";

const props = defineProps({
  // 滚动的速度
  speed: {
    type: Number,
    default: 0.5,
  },
  // 滚动模式
  mode: {
    type: String,
    default: "vertical", // 水平滚动：horizontal
  },
  // 滚动方向
  direction: {
    type: String,
    default: "top", // 滚动方向：top/bottom/left/right
  },
  // 是否自动滚动，适用于挂载时dom已经生成的情况
  scroll: {
    type: Boolean,
    default: true,
  },
  // 是否开启鼠标移入移除事件
  mouseEvent: {
    type: Boolean,
    default: true,
  },
  // 是否复制第一个元素到最后面，适合循环的场景
  copy: {
    type: Boolean,
    default: true,
  },
});

// 无限滚动逻辑
const scrollBoxRef = ref(); // 滚动元素
const scrollContentRef = ref(); // 实际滚动内容盒子
const isScroll = ref(false); // 是否开启滚动
const scrollDistance = ref(0); // 滚动的距离
const isCopy = ref(false); // 是否复制
const isHover = ref(false); // 鼠标是否移入
let fatherBoxNum = 0; // 父元素的高度
let scrollBoxNum = 0; // 滚动元素的高度
let offset = 20; // 偏移量，padding的上下或左右的和

// 滚动区域的滚动样式
const scrollBoxStyle = computed(() => {
  return {
    transform:
      props.mode === "vertical"
        ? `translateY(${scrollDistance.value * flag}px)`
        : `translateX(${scrollDistance.value * flag}px)`,
  };
});

// 辅助计算滚动方向的函数
let flag = -1; // 系数，-1代表是向上或向左滚动，1代表是向下或者向右滚动
let getDirection = () => {
  let _direction = props.direction;
  // 如果是水平方向滚动的话，滚动方向设置的不是向左或者向右，则默认向左滚动
  if (props.mode === "horizontal" && !["left", "right"].includes(_direction)) {
    _direction = "left";
  }
  if (_direction === "bottom" || _direction === "right") {
    flag = 1;
  }
  getDirection = () => flag;
  return flag;
};

// 滚动速度值
let _speed = 0;

onMounted(() => {
  _speed = Math.abs(props.speed);
  getDirection();
  if (props.scroll) {
    judgeScroll();
  }
});

// 开启滚动
const start = () => {
  judgeScroll();
};

// 停止滚动
const stop = (isReset: boolean = true) => {
  stopScroll(isReset);
};

// 监听滚动元素的鼠标移入事件
const onMouseoverOfScroll = (e: any) => {
  stopScroll(false);
  isHover.value = true;
};

// 监听滚动元素的鼠标移出事件
const onMouseoutOfScroll = (e: any) => {
  isScroll.value = true;
  isHover.value = false;
  startScroll();
};

// 判断内容是否满足滚动条件，如果满足条件则开启滚动
const judgeScroll = () => {
  nextTick(() => {
    // 垂直滚动
    if (props.mode === "vertical") {
      fatherBoxNum = scrollBoxRef.value.clientHeight;
      scrollBoxNum = scrollContentRef.value.clientHeight;
    }
    // 水平滚动
    if (props.mode === "horizontal") {
      fatherBoxNum = scrollBoxRef.value.clientWidth;
      scrollBoxNum = scrollContentRef.value.clientWidth;
    }
    if (scrollBoxNum > fatherBoxNum) {
      isScroll.value = true;
    } else {
      isScroll.value = false;
    }
    scroll();
  });
};

// 重置向下或向右滚动时的初始滚动距离
const resetDistance = () => {
  scrollDistance.value = fatherBoxNum - scrollBoxNum - offset;
};

// 滚动函数
const scroll = () => {
  // 表示向下或向右滚动
  if (flag === 1) {
    resetDistance();
  }
  if (isScroll.value) {
    startScroll();
  } else {
    stopScroll();
  }
};

// 开启滚动
const startScroll = () => {
  if (!isScroll.value) return;
  requestAnimationFrame(srcollCallback);
  if (!props.mouseEvent) return;
  if (scrollBoxRef.value && !scrollBoxRef.value.onmouseover)
    scrollBoxRef.value.onmouseover = onMouseoverOfScroll;
  if (scrollBoxRef.value && !scrollBoxRef.value.onmouseout)
    scrollBoxRef.value.onmouseout = onMouseoutOfScroll;
};

// 滚动的回调函数
const srcollCallback = () => {
  if (!isScroll.value) return;
  scrollDistance.value += _speed;
  // 向下或者向右滚动
  if (flag === 1) {
    if (scrollDistance.value >= 0) {
      if (!props.copy) {
        if (scrollDistance.value >= fatherBoxNum) {
          // 重新设置滚动距离
          resetDistance();
        }
      } else if (!isCopy.value) {
        scrollContentRef.value.appendChild(
          scrollContentRef.value.firstElementChild.cloneNode(true)
        );
        isCopy.value = true;
        // 设置当前滚动的距离为滚动内容的宽度或者高度，并取反
        scrollDistance.value += scrollBoxNum;
        scrollDistance.value *= -1;
      }
    }
    if (scrollDistance.value >= fatherBoxNum - scrollBoxNum - offset) {
      // 表示第一轮已经滚动完毕
      if (isCopy.value) {
        scrollContentRef.value.removeChild(
          scrollContentRef.value.lastElementChild
        );
        isCopy.value = false;
        // 重新设置滚动距离
        resetDistance();
      }
    }
  } else {
    // 向上或向左的逻辑
    if (scrollDistance.value >= scrollBoxNum - fatherBoxNum + offset) {
      if (!isCopy.value && props.copy) {
        scrollContentRef.value.appendChild(
          scrollContentRef.value.firstElementChild.cloneNode(true)
        );
        isCopy.value = true;
      } else {
        // 滚动距离大于等于了滚动区域最大的高度时
        if (scrollDistance.value >= scrollBoxNum) {
          scrollDistance.value = 0;
          isCopy.value = false;
          if (props.copy) {
            scrollContentRef.value.removeChild(
              scrollContentRef.value.lastElementChild
            );
          }
        }
      }
    }
  }
  // 再一次调用
  requestAnimationFrame(srcollCallback);
};

// 停止滚动
const stopScroll = (isReset: boolean = true) => {
  isScroll.value = false;
  if (isReset) {
    if (flag === 1) {
      resetDistance();
    } else {
      scrollDistance.value = 0;
    }
    if (!props.mouseEvent) return;
    if (scrollBoxRef.value && scrollBoxRef.value.onmouseover)
      scrollBoxRef.value.onmouseover = null;
    if (scrollBoxRef.value && scrollBoxRef.value.onmouseout)
      scrollBoxRef.value.onmouseout = null;
  }
};

defineExpose({
  start,
  stop,
});
</script>

<style scoped>
.cursor-pointer {
  cursor: pointer;
}

.container {
  height: 100%;
  width: 100%;
  box-sizing: border-box;
}
.scroll-box {
  width: 100%;
  height: 100%;
  overflow: hidden;
  scrollbar-width: none;
  padding: 10px;
  box-sizing: border-box;
  scrollbar-width: none; /* firefox */
  -ms-overflow-style: none; /* IE 10+ */
}
/* 谷歌和safari */
.scroll-box::-webkit-scrollbar {
  display: none;
}

.content-box {
  display: flex;
  height: max-content;
  width: max-content;
}

.flex-row {
  flex-direction: row;
}

.flex-col {
  flex-direction: column;
}
</style>
